ruby3mf 0.1.11 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b924df1af6b462ddf619c0cd9d1d4d21435275d
4
- data.tar.gz: 960b2be3c49a86a51adf0ba7882434e74a7317a8
3
+ metadata.gz: 53cf0043b4bcebef905de98f2137ad6db1b36fc4
4
+ data.tar.gz: 3a970b15e198109bf6638f7206569e0b90bc51fc
5
5
  SHA512:
6
- metadata.gz: 99e441a31af2a9a92392da4afc48469e7f286f97bf93d0cbfbae12ca6c5445c3e2b0276415aa85bf57285fec12650a18a12e1c25e07970bbe75ca9e9e08027aa
7
- data.tar.gz: 95fb5fcca5571bb3925fb20318a088cba5d7e8fa7969fcd3ef00156cb3fd1903fd1ae2174281f4cc12e678ef43ae252dc5c761230bf12da95c7aa0f33d29fd63
6
+ metadata.gz: 91a20f9e6da5bf9f89b582509764e0ef3cf0a320c798c0020fd5fc4fd6e9e1a931193005549c0777e30b19292fcb33b7b044fee6ff70ba6ec8b36fcd0411e7e7
7
+ data.tar.gz: a2203b11cb4f4ecdd9ec342ae73ae5fa2b59e46d58474671ff2123e7dd88c3438c85d7cfe54bfb3c59ac024f501dfd8ed6a818a43647dc02d9bb3866ca137095
data/bin/cli.rb CHANGED
@@ -3,3 +3,10 @@
3
3
  require_relative '../lib/ruby3mf'
4
4
 
5
5
  doc = Document.read(ARGV.first)
6
+
7
+ errors = Log3mf.entries(:error, :fatal_error)
8
+ puts "Validating file: #{ARGV.first}"
9
+ errors.each do |ent|
10
+ h = { context: ent[0], severity: ent[1], message: ent[2] }
11
+ puts h
12
+ end
data/lib/ruby3mf.rb CHANGED
@@ -6,14 +6,15 @@ require_relative "ruby3mf/model3mf"
6
6
  require_relative "ruby3mf/relationships"
7
7
  require_relative "ruby3mf/thumbnail3mf"
8
8
  require_relative "ruby3mf/texture3mf"
9
+ require_relative "ruby3mf/global_xml_validations"
9
10
 
10
11
  require 'zip'
11
12
  require 'nokogiri'
12
13
  require 'json'
13
14
  require 'mimemagic'
14
- # require 'csv'
15
+ require 'uri'
16
+ require 'yaml'
15
17
 
16
18
  module Ruby3mf
17
-
18
19
  # Your code goes here...
19
20
  end
@@ -6,9 +6,7 @@ class ContentTypes
6
6
  Log3mf.context "parse" do |l|
7
7
  begin
8
8
 
9
- doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
10
- config.strict.nonet.noblanks
11
- end
9
+ doc = GlobalXMLValidations.validate_parse(zip_entry)
12
10
 
13
11
  l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
14
12
  l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
@@ -28,7 +28,6 @@ class Document
28
28
  def self.read(input_file)
29
29
 
30
30
  m = new(input_file)
31
-
32
31
  begin
33
32
  Log3mf.context "zip" do |l|
34
33
  begin
@@ -43,6 +42,25 @@ class Document
43
42
 
44
43
  l.info "Zip file is valid"
45
44
 
45
+ # check for valid, absolute URI's for each path name
46
+
47
+ zip_file.each do |part|
48
+ l.context "part names /#{part.name}" do |l|
49
+ unless part.name.end_with? '[Content_Types].xml'
50
+ begin
51
+ u = URI part.name
52
+ rescue ArgumentError
53
+ l.error :err_uri_bad
54
+ next
55
+ end
56
+
57
+ u.path.split('/').each do |segment|
58
+ l.error :err_uri_hidden_file if (segment.start_with? '.') && !(segment.end_with? '.rels')
59
+ end
60
+ end
61
+ end
62
+ end
63
+
46
64
  l.context "content types" do |l|
47
65
  # 1. Get Content Types
48
66
  content_type_match = zip_file.glob('\[Content_Types\].xml').first
@@ -71,7 +89,19 @@ class Document
71
89
  # 3. Validate all relationships
72
90
  m.relationships.each do |rel|
73
91
  l.context rel[:target] do |l|
92
+
93
+ begin
94
+ u = URI rel[:target]
95
+ rescue URI::InvalidURIError
96
+ l.error :err_uri_bad
97
+ next
98
+ end
99
+
100
+ l.error :err_uri_relative_path unless u.to_s.start_with? '/'
101
+
74
102
  target = rel[:target].gsub(/^\//, "")
103
+ l.error :err_uri_empty_segment if target.end_with? '/' or target.include? '//'
104
+ l.error :err_uri_relative_path if target.include? '/../'
75
105
  relationship_file = zip_file.glob(target).first
76
106
 
77
107
  if relationship_file
@@ -92,6 +122,7 @@ class Document
92
122
  end
93
123
  end
94
124
  end
125
+
95
126
  return m
96
127
  rescue Zip::Error
97
128
  l.fatal_error 'File provided is not a valid ZIP archive', page: 9
@@ -123,7 +154,7 @@ class Document
123
154
  end
124
155
  end
125
156
 
126
- File.open(output_file, "wb") {|f| f.write(buffer.string) }
157
+ File.open(output_file, "wb") { |f| f.write(buffer.string) }
127
158
 
128
159
  end
129
160
 
@@ -0,0 +1,61 @@
1
+ class EdgeList
2
+
3
+ def initialize()
4
+ @edges = { }
5
+ end
6
+
7
+ def add_edge(first_vertex, second_vertex)
8
+ if first_vertex < second_vertex
9
+ edge = "#{first_vertex}:#{second_vertex}"
10
+ else
11
+ edge = "#{second_vertex}:#{first_vertex}"
12
+ end
13
+
14
+ (pos_count, neg_count) = @edges[edge]
15
+
16
+ if pos_count == nil or neg_count == nil
17
+ pos_count = 0
18
+ neg_count = 0
19
+ end
20
+
21
+ pos_count += 1 if first_vertex < second_vertex
22
+ neg_count += 1 if second_vertex < first_vertex
23
+
24
+ @edges[edge] = [pos_count, neg_count]
25
+ end
26
+
27
+ def edge_count(first_vertex, second_vertex)
28
+ if first_vertex < second_vertex
29
+ edge = "#{first_vertex}:#{second_vertex}"
30
+ else
31
+ edge = "#{second_vertex}:#{first_vertex}"
32
+ end
33
+
34
+ @edges[edge]
35
+ end
36
+
37
+ def print_list()
38
+ @edges.each do |key, value|
39
+ (pos, neg) = value
40
+ puts "#{pos} : #{neg}"
41
+ end
42
+ end
43
+
44
+ def verify_edges()
45
+ @edges.each do |key, value|
46
+ (pos, neg) = value
47
+
48
+ if (pos > 1 and neg == 0) or (pos == 0 and neg > 1)
49
+ return :bad_orientation
50
+ elsif pos + neg == 1
51
+ return :hole
52
+ elsif pos != 1 or neg != 1
53
+ return :nonmanifold
54
+ end
55
+ end
56
+
57
+ :ok
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,96 @@
1
+ resource_contentype_invalid:
2
+ msg: "resource in model has invalid contenttype %{bt}"
3
+ page: 10
4
+ err_uri_empty_segment:
5
+ msg: 'No segment of a 3MF part name path may be empty'
6
+ page: 13
7
+ err_uri_hidden_file:
8
+ msg: "Other than /_rels/.rels, no segment of a 3MF part name may start with the '.' character"
9
+ page: 13
10
+ err_uri_bad:
11
+ msg: 'Path names must be valid Open Package Convention URIs or IRIs'
12
+ page: 13
13
+ err_uri_relative_path:
14
+ msg: 'Part names must not include relative paths'
15
+ page: 13
16
+ model_resource_not_in_rels:
17
+ msg: "Missing required resource: %{mr} Resource referenced in model, but not in .rels relationship file"
18
+ page: 10
19
+ resource_3dmodel_orientation:
20
+ msg: "Bad triangle orientation"
21
+ page: 27
22
+ resource_3dmodel_hole:
23
+ msg: "Hole in model"
24
+ page: 27
25
+ resource_3dmodel_nonmanifold:
26
+ msg: "Non-manifold edge in 3dmodel"
27
+ page: 27
28
+ 3d_model_invalid_xml:
29
+ msg: "Model file invalid XML. Exception Start tag expected, '<' not found"
30
+ page:
31
+ 3d_payload_files:
32
+ msg: "Relationship Target file /3D/Texture/texture2.texture not found"
33
+ page: 11
34
+ content_types_invalid_xml:
35
+ msg: "[Content_Types].xml file is not valid XML. Start tag expected, '<' not found"
36
+ page: 15
37
+ dot_rels_file_has_invalid_xml:
38
+ msg: "Relationships (.rel) file is not a valid XML file: Start tag expected, '<' not found"
39
+ page: 4
40
+ dot_rels_file_missing_relationships_element:
41
+ msg: ".rels XML must have &lt;Relationships&gt; root element"
42
+ page: 4
43
+ dot_rels_file_no_relationship_element:
44
+ msg: "No relationship elements found"
45
+ page: 4
46
+ invalid_content_type:
47
+ msg: "[Content_Types].xml is missing required ContentType \"application/vnd.openxmlformats-package.relationships+xml\""
48
+ page: 10
49
+ invalid_startpart_type:
50
+ msg: "rels/.rels Relationship file has an invalid attribute type for the root 3D Model (StartPart). The required type is \"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\""
51
+ page: 10
52
+ invalid_thumbnail_colorspace:
53
+ msg: "CMYK JPEG images must not be used for the thumbnail"
54
+ page: 36
55
+ invalid_thumbnail_file_type:
56
+ msg: "Expected a png or jpeg thumbnail but the thumbnail was of type image/gif"
57
+ page: 36
58
+ invalid_texture_file_type:
59
+ msg: "Expected a png or jpeg texture but the texture was of type image/gif"
60
+ page: 16
61
+ missing_content_types:
62
+ msg: "Missing required file: [Content_Types].xml"
63
+ page: 4
64
+ missing_dot_rels_file:
65
+ msg: "Missing required file _rels/.rels"
66
+ page: 4
67
+ missing_rels_folder:
68
+ msg: "Missing required file _rels/.rels"
69
+ page: 4
70
+ multiple_relationships:
71
+ msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
72
+ page: 10
73
+ no_3d_model:
74
+ msg: "Relationship Target file /3D/3dmodel.model not found"
75
+ page: 11
76
+ not_a_zip:
77
+ msg: "File provided is not a valid ZIP archive"
78
+ page: 9
79
+ zero_size_texture:
80
+ msg: "Texture file must be valid image file"
81
+ page: 16
82
+ resource_contentype_invalid:
83
+ msg: "resource in model has invalid contenttype image/ping"
84
+ page: 10
85
+ has_xml_space_attribute:
86
+ msg: "found an xml:space attribute when it is not allowed"
87
+ page: 16
88
+ wrong_encoding:
89
+ msg: "found XML content that was not UTF8 encoded"
90
+ page: 15
91
+ missing_object_pid:
92
+ msg: "object with triangle color override missing object level pid"
93
+ page: 26
94
+ missing_model_children:
95
+ msg: "model element must include resources and build as child elements"
96
+ page: 20
@@ -0,0 +1,36 @@
1
+ class GlobalXMLValidations
2
+
3
+ def self.validate_parse(file)
4
+ doc = Nokogiri::XML(file.get_input_stream) do |config|
5
+ config.strict.nonet.noblanks
6
+ end
7
+ validate(file, doc)
8
+ doc
9
+ end
10
+
11
+ def self.validate(file, document)
12
+ Log3mf.context "global xml validations" do |l|
13
+ l.error :has_xml_space_attribute if space_attribute_exists?(document)
14
+ l.error :wrong_encoding if xml_not_utf8_encoded?(document)
15
+ l.error :dtd_not_allowed if dtd_exists?(file)
16
+ end
17
+ end
18
+
19
+ def self.space_attribute_exists?(document)
20
+ !(document.xpath('//*[@xml:space]').empty?)
21
+ end
22
+
23
+ def self.xml_not_utf8_encoded?(document)
24
+ !(document.encoding == 'UTF-8' || document.encoding == 'utf-8')
25
+ end
26
+
27
+ def self.dtd_exists?(file)
28
+ found = file.get_input_stream.find { |line| line =~ /(!DOCTYPE\b)|(!ELEMENT\b)|(!ENTITY\b)|(!NOTATION\b)|(!ATTLIST\b)/ }
29
+ !found.nil?
30
+ end
31
+ end
32
+
33
+
34
+
35
+
36
+
@@ -1,4 +1,5 @@
1
1
  require 'singleton'
2
+ require 'yaml'
2
3
 
3
4
  # Example usage:
4
5
 
@@ -33,6 +34,8 @@ class Log3mf
33
34
  @log_list = []
34
35
  @context_stack = []
35
36
  @ledger = []
37
+ errormap_path = File.join(File.dirname(__FILE__),"errors.yml")
38
+ @errormap = YAML.load_file(errormap_path)
36
39
  end
37
40
 
38
41
  def reset_log
@@ -68,11 +71,20 @@ class Log3mf
68
71
  end
69
72
 
70
73
  def log(severity, message, options={})
71
- @log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
72
- # puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
74
+ if message.is_a?(Symbol)
75
+ new_log(severity, message, options)
76
+ else
77
+ @log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
78
+ # puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
79
+ end
73
80
  raise FatalError if severity == :fatal_error
74
81
  end
75
82
 
83
+ def new_log(severity, message, options={})
84
+ error_info = @errormap.fetch(message.to_s)
85
+ @log_list << ["#{@context_stack.join("/")}", severity, error_info["msg"], page: error_info["page"]] unless severity==:debug && ENV['LOGDEBUG'].nil?
86
+ end
87
+
76
88
  def count_entries(*levels)
77
89
  entries(*levels).count
78
90
  end
@@ -120,4 +132,3 @@ class Log3mf
120
132
  Log3mf.instance.to_json
121
133
  end
122
134
  end
123
-
@@ -0,0 +1,78 @@
1
+ require_relative 'edge_list'
2
+
3
+
4
+ def find_child(node, child_name)
5
+ node.children.each do |child|
6
+ if child.name == child_name
7
+ return child
8
+ end
9
+ end
10
+
11
+ nil
12
+ end
13
+
14
+
15
+ class MeshAnalyzer
16
+
17
+ def self.validate_object(object)
18
+ Log3mf.context "validating geometry" do |l|
19
+ list = EdgeList.new
20
+
21
+ # if a triangle has a pid, then the object needs a pid
22
+ has_triangle_pid = false
23
+
24
+ mesh = find_child(object, "mesh")
25
+ if mesh
26
+ triangles = find_child(mesh, "triangles")
27
+
28
+ if triangles
29
+ triangles.children.each do |triangle|
30
+ v1 = triangle.attributes["v1"].to_s().to_i()
31
+ v2 = triangle.attributes["v2"].to_s().to_i()
32
+ v3 = triangle.attributes["v3"].to_s().to_i()
33
+
34
+ list.add_edge(v1, v2)
35
+ list.add_edge(v2, v3)
36
+ list.add_edge(v3, v1)
37
+
38
+ if not has_triangle_pid
39
+ has_triangle_pid = triangle.attributes["pid"] != nil
40
+ end
41
+ end
42
+
43
+ has_object_material = object.attributes["pid"] and object.attributes["pindex"]
44
+ if has_triangle_pid and not has_object_material
45
+ l.error :missing_object_pid
46
+ end
47
+
48
+ result = list.verify_edges()
49
+ if result == :bad_orientation
50
+ l.fatal_error :resource_3dmodel_orientation
51
+ elsif result == :hole
52
+ l.fatal_error :resource_3dmodel_hole
53
+ elsif result == :nonmanifold
54
+ l.fatal_error :resource_3dmodel_nonmanifold
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.validate(model_doc)
62
+ root = model_doc.root
63
+ node = root
64
+
65
+ if node.name == "model"
66
+ resources = find_child(node, "resources")
67
+
68
+ if resources
69
+ resources.children.each do |resource|
70
+ solid_model = resource.attributes["type"].to_s() == "model" or resource.attributes["type"].to_s() == "solidsupport"
71
+ if resource.name == "object" and solid_model
72
+ validate_object(resource)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'mesh_analyzer'
2
+
1
3
  class Model3mf
2
4
 
3
5
  def self.parse(document, zip_entry)
@@ -5,10 +7,7 @@ class Model3mf
5
7
 
6
8
  Log3mf.context "parsing model" do |l|
7
9
  begin
8
- model_doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
9
- config.strict.nonet.noblanks
10
- end
11
- l.info "We Found a Model, and it's XML!"
10
+ model_doc = GlobalXMLValidations.validate_parse(zip_entry)
12
11
  rescue Nokogiri::XML::SyntaxError => e
13
12
  l.fatal_error "Model file invalid XML. Exception #{e}"
14
13
  end
@@ -25,7 +24,7 @@ class Model3mf
25
24
  l.info "All model required resources are defined in .rels relationship files."
26
25
  else
27
26
  missing_resources.each { |mr|
28
- l.error "Missing required resource: #{mr} Resource referenced in model, but not in .rels relationship file", page: 10
27
+ l.error :model_resource_not_in_rels, mr: mr
29
28
  }
30
29
  end
31
30
 
@@ -40,13 +39,22 @@ class Model3mf
40
39
  l.info "All model resource contenttypes are valid"
41
40
  else
42
41
  bad_types.each { |bt|
43
- puts "bad type: #{bt}"
44
- l.error "resource in model has invalid contenttype #{bt}", page: 10
42
+ l.error :resource_contentype_invalid, bt: bt
45
43
  }
46
44
  end
47
45
  end
48
46
 
49
47
  end
48
+
49
+ l.context "verifying model structure" do |l|
50
+ root = model_doc.root
51
+ l.error :root_3dmodel_element_not_model if root.name != "model"
52
+
53
+ children = model_doc.root.children.map { |child| child.name }
54
+ l.error :missing_model_children unless children.include?("resources") && children.include?("build")
55
+ end
56
+
57
+ MeshAnalyzer.validate(model_doc)
50
58
  end
51
59
  model_doc
52
60
  end
@@ -5,9 +5,7 @@ class Relationships
5
5
  Log3mf.context "parsing relationships" do |l|
6
6
  begin
7
7
  # Parse Relationships XML
8
- doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
9
- config.strict.nonet.noblanks
10
- end
8
+ doc = GlobalXMLValidations.validate_parse(zip_entry)
11
9
 
12
10
  # Verify <Relationships><Relationship/></Relationships>
13
11
  root_element = doc.children[0]
@@ -16,6 +14,11 @@ class Relationships
16
14
  if relationship_elements.size > 0
17
15
  relationship_elements.each do |node|
18
16
  if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
17
+ relationships.each do |previous_rel|
18
+ if previous_rel[:target] == node['Target'] && previous_rel[:type] == node['Type']
19
+ l.error :multiple_relationships
20
+ end
21
+ end
19
22
  relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
20
23
  l.info "adding relationship: #{relationships.last.inspect}"
21
24
  else
@@ -30,8 +33,7 @@ class Relationships
30
33
  start_part_type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
31
34
  start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
32
35
  if start_part_count != 1
33
- l.error "rels/.rels Relationship file has an invalide attribute type for the root 3D Model (StartPart).
34
- The correct attribute type should be \"#{start_part_type}\"", page: 10
36
+ l.error :invalid_startpart_type
35
37
  end
36
38
  end
37
39
  end
@@ -6,7 +6,6 @@ class Texture3mf
6
6
  end
7
7
 
8
8
  def self.parse(document, relationship_file)
9
-
10
9
  t = new(document)
11
10
  t.name = relationship_file.name
12
11
  stream = relationship_file.get_input_stream
@@ -1,12 +1,18 @@
1
+ require 'mini_magick'
2
+
1
3
  class Thumbnail3mf
2
4
 
3
5
  def self.parse(doc, relationship_file)
4
6
  img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
7
+ img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
8
+
5
9
  Log3mf.context "Thumbnail3mf" do |l|
6
10
  l.fatal_error "thumbnail file must be valid image file", page: 10 unless img_type
11
+ l.fatal_error "CMYK JPEG images must not be used for the thumbnail", page: 36 if img_colorspace.include? "CMYK"
7
12
  l.debug "thumbnail is of type: #{img_type}"
8
13
  l.error "Expected a png or jpeg thumbnail but the thumbnail was of type #{img_type}", page: 12 unless ['image/png', 'image/jpeg'].include? img_type.type
9
14
  end
10
15
 
11
16
  end
17
+
12
18
  end
@@ -1,3 +1,3 @@
1
1
  module Ruby3mf
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.13"
3
3
  end
data/ruby3mf.gemspec CHANGED
@@ -14,14 +14,6 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "https://github.com/IPGPTP/ruby3mf"
15
15
  spec.license = "MIT"
16
16
 
17
- # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
- # delete this section to allow pushing this gem to any host.
19
- # if spec.respond_to?(:metadata)
20
- # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
- # else
22
- # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
- # end
24
-
25
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
18
  spec.bindir = "exe"
27
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -35,4 +27,5 @@ Gem::Specification.new do |spec|
35
27
  spec.add_runtime_dependency 'rubyzip'
36
28
  spec.add_runtime_dependency 'nokogiri', '~>1.6.8'
37
29
  spec.add_runtime_dependency 'mimemagic'
30
+ spec.add_runtime_dependency 'mini_magick', '~> 4.6'
38
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby3mf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Whitmarsh, Jeff Porter, and William Hertling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-21 00:00:00.000000000 Z
11
+ date: 2017-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: mini_magick
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.6'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.6'
111
125
  description: Read, write and validate 3MF files with Ruby easily.
112
126
  email:
113
127
  - mwhit@hp.com
@@ -133,7 +147,11 @@ files:
133
147
  - lib/ruby3mf.rb
134
148
  - lib/ruby3mf/content_types.rb
135
149
  - lib/ruby3mf/document.rb
150
+ - lib/ruby3mf/edge_list.rb
151
+ - lib/ruby3mf/errors.yml
152
+ - lib/ruby3mf/global_xml_validations.rb
136
153
  - lib/ruby3mf/log3mf.rb
154
+ - lib/ruby3mf/mesh_analyzer.rb
137
155
  - lib/ruby3mf/model3mf.rb
138
156
  - lib/ruby3mf/relationships.rb
139
157
  - lib/ruby3mf/texture3mf.rb