From 04d59ff291fface29825da761fe206cf4573e4f9 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sat, 7 Sep 2024 23:36:04 +0200 Subject: [PATCH 1/3] Fix issue #12: Scale geometry when importing shapes The geometry data is recomputed (positions, size, transformations), without modifying the transformation matrix. --- source/__init__.py | 22 +++++++++++++ source/import_x3d.py | 75 +++++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/source/__init__.py b/source/__init__.py index 9f4b1fd..546cb83 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -37,6 +37,26 @@ class ImportX3D(bpy.types.Operator, ImportHelper): filename_ext = ".x3d" filter_glob: StringProperty(default="*.x3d;*.wrl", options={'HIDDEN'}) + file_unit: EnumProperty( + name="File Unit", + items=(('M', "Meter", "The VRML/x3D specification states that data have to be exported in Meter."), + ('DM', "Decimeter", ""), + ('CM', "Centimeter", ""), + ('MM', "Milimeter", ""), + ('IN', "Inch", ""), + ('CUSTOM', "Use scale", "Prefer scaling the geometry with the scale factor below"), + ), + description="Unit used in the input file", + default='M', + ) + + global_scale: FloatProperty( + name="Scale", + min=0.001, max=1000.0, + default=1.0, + description="Scale value used when 'File Unit' is set to 'Use scale'", + ) + def execute(self, context): from . import import_x3d @@ -248,6 +268,8 @@ class X3D_PT_import_transform(bpy.types.Panel): sfile = context.space_data operator = sfile.active_operator + layout.prop(operator, "file_unit") + layout.prop(operator, "global_scale") layout.prop(operator, "axis_forward") layout.prop(operator, "axis_up") diff --git a/source/import_x3d.py b/source/import_x3d.py index 00b2d5d..baee73c 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -15,6 +15,7 @@ from itertools import chain texture_cache = {} material_cache = {} +conversionScale = 1.0 EPSILON = 0.0000001 # Very crude. @@ -681,7 +682,7 @@ class vrmlNode(object): print('\tvalue "%s" could not be used as an int for field "%s"' % (f[0], field)) return default - def getFieldAsFloat(self, field, default, ancestry): + def getFieldAsFloat(self, field, default, ancestry, s=1.0): self_real = self.getRealNode() # in case we're an instance f = self_real.getFieldName(field, ancestry) @@ -695,12 +696,12 @@ class vrmlNode(object): return default try: - return float(f[0]) + return float(f[0])*s except: print('\tvalue "%s" could not be used as a float for field "%s"' % (f[0], field)) return default - def getFieldAsFloatTuple(self, field, default, ancestry): + def getFieldAsFloatTuple(self, field, default, ancestry, s=1.0): self_real = self.getRealNode() # in case we're an instance f = self_real.getFieldName(field, ancestry) @@ -716,7 +717,7 @@ class vrmlNode(object): for v in f: if v != ',': try: - ret.append(float(v.strip('"'))) + ret.append(float(v.strip('"'))*s) except: break # quit of first non float, perhaps its a new field name on the same line? - if so we are going to ignore it :/ TODO # print(ret) @@ -774,7 +775,7 @@ class vrmlNode(object): print('\tvalue "%s" could not be used as a string for field "%s"' % (f[0], field)) return default - def getFieldAsArray(self, field, group, ancestry): + def getFieldAsArray(self, field, group, ancestry, s=1.0): """ For this parser arrays are children """ @@ -814,6 +815,7 @@ class vrmlNode(object): # print(child_array) # Normal vrml array_data = child_array.array_data + apply_scale = s != 1.0 # print('array_data', array_data) if group == -1 or len(array_data) == 0: @@ -829,6 +831,11 @@ class vrmlNode(object): # make a flat array if flat: flat_array = array_data # we are already flat. + if apply_scale: + # applying scale + for item in array_data: + item *= s + else: flat_array = [] @@ -837,6 +844,10 @@ class vrmlNode(object): if type(item) == list: extend_flat(item) else: + if apply_scale: + # applying scale + item *= s + flat_array.append(item) extend_flat(array_data) @@ -1498,11 +1509,11 @@ def translateScale(sca): def translateTransform(node, ancestry): - cent = node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0, 0.0) + cent = node.getFieldAsFloatTuple('center', None, ancestry, conversionScale) # (0.0, 0.0, 0.0) rot = node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) - sca = node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0, 1.0) + sca = node.getFieldAsFloatTuple('scale', None, ancestry, conversionScale) # (1.0, 1.0, 1.0) scaori = node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) - tx = node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0, 0.0) + tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversionScale) # (0.0, 0.0, 0.0) if cent: cent_mat = Matrix.Translation(cent) @@ -1542,10 +1553,10 @@ def translateTransform(node, ancestry): def translateTexTransform(node, ancestry): - cent = node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0) + cent = node.getFieldAsFloatTuple('center', None, ancestry, conversionScale) # (0.0, 0.0) rot = node.getFieldAsFloat('rotation', None, ancestry) # 0.0 sca = node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0) - tx = node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0) + tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversionScale) # (0.0, 0.0) if cent: # cent is at a corner by default @@ -1919,7 +1930,7 @@ def importMesh_IndexedFaceSet(geom, ancestry): # TODO: resolve that somehow, so that vertex set can be effectively # reused between different mesh types? else: - points = coord.getFieldAsArray('point', 3, ancestry) + points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) if coord.canHaveReferences(): coord.parsed = points index = geom.getFieldAsArray('coordIndex', 0, ancestry) @@ -2430,7 +2441,7 @@ def importMesh_LineSet(geom, ancestry): # TODO: line display properties are ignored # Per-vertex color is ignored coord = geom.getChildBySpec('Coordinate') - src_points = coord.getFieldAsArray('point', 3, ancestry) + src_points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) # Array of 3; Blender needs arrays of 4 bpycurve = bpy.data.curves.new("LineSet", 'CURVE') bpycurve.dimensions = '3D' @@ -2456,7 +2467,7 @@ def importMesh_IndexedLineSet(geom, ancestry): # coord = geom.getChildByName('coord') # 'Coordinate' coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml if coord: - points = coord.getFieldAsArray('point', 3, ancestry) + points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) else: points = [] @@ -2499,7 +2510,7 @@ def importMesh_PointSet(geom, ancestry): # VRML not x3d coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml if coord: - points = coord.getFieldAsArray('point', 3, ancestry) + points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) else: points = [] @@ -2529,7 +2540,7 @@ def importMesh_Sphere(geom, ancestry): # solid is ignored. # Extra field 'subdivision="n m"' attribute, specifying how many # rings and segments to use (X3DOM). - r = geom.getFieldAsFloat('radius', 0.5, ancestry) + r = geom.getFieldAsFloat('radius', 0.5, ancestry, conversionScale) subdiv = geom.getFieldAsArray('subdivision', 0, ancestry) if subdiv: if len(subdiv) == 1: @@ -2624,8 +2635,8 @@ def importMesh_Cylinder(geom, ancestry): # solid is ignored # no ccw in this element # Extra parameter subdivision="n" - how many faces to use - radius = geom.getFieldAsFloat('radius', 1.0, ancestry) - height = geom.getFieldAsFloat('height', 2, ancestry) + radius = geom.getFieldAsFloat('radius', 1.0, ancestry, conversionScale) + height = geom.getFieldAsFloat('height', 2, ancestry, conversionScale) bottom = geom.getFieldAsBool('bottom', True, ancestry) side = geom.getFieldAsBool('side', True, ancestry) top = geom.getFieldAsBool('top', True, ancestry) @@ -2682,8 +2693,8 @@ def importMesh_Cone(geom, ancestry): # Solid ignored # Extra parameter subdivision="n" - how many faces to use n = geom.getFieldAsInt('subdivision', GLOBALS['CIRCLE_DETAIL'], ancestry) - radius = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry) - height = geom.getFieldAsFloat('height', 2, ancestry) + radius = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry, conversionScale) + height = geom.getFieldAsFloat('height', 2, ancestry, conversionScale) bottom = geom.getFieldAsBool('bottom', True, ancestry) side = geom.getFieldAsBool('side', True, ancestry) @@ -2722,7 +2733,7 @@ def importMesh_Cone(geom, ancestry): def importMesh_Box(geom, ancestry): # Solid is ignored # No ccw in this element - (dx, dy, dz) = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry) + (dx, dy, dz) = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry, conversionScale) dx /= 2 dy /= 2 dz /= 2 @@ -3275,9 +3286,9 @@ def importLamp_PointLight(node, ancestry): # attenuation = node.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry) intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher. - location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry) + location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversionScale) # is_on = node.getFieldAsBool('on', True, ancestry) # TODO - radius = node.getFieldAsFloat('radius', 100.0, ancestry) + radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversionScale) bpylamp = bpy.data.lights.new(vrmlname, 'POINT') bpylamp.energy = intensity @@ -3324,9 +3335,9 @@ def importLamp_SpotLight(node, ancestry): cutOffAngle = node.getFieldAsFloat('cutOffAngle', 0.785398, ancestry) * 2.0 # max is documented to be 1.0 but some files have higher. direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry) intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher. - location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry) + location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversionScale) # is_on = node.getFieldAsBool('on', True, ancestry) # TODO - radius = node.getFieldAsFloat('radius', 100.0, ancestry) + radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversionScale) bpylamp = bpy.data.lights.new(vrmlname, 'SPOT') bpylamp.energy = intensity @@ -3378,7 +3389,7 @@ def importViewpoint(bpycollection, node, ancestry, global_matrix): fieldOfView = node.getFieldAsFloat('fieldOfView', 0.785398, ancestry) # max is documented to be 1.0 but some files have higher. # jump = node.getFieldAsBool('jump', True, ancestry) orientation = node.getFieldAsFloatTuple('orientation', (0.0, 0.0, 1.0, 0.0), ancestry) - position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry) + position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry, conversionScale) description = node.getFieldAsString('description', '', ancestry) bpycam = bpy.data.cameras.new(name) @@ -3590,12 +3601,22 @@ def load_web3d( *, PREF_FLAT=False, PREF_CIRCLE_DIV=16, + file_unit='M', + global_scale=1.0, global_matrix=None, HELPER_FUNC=None ): # Used when adding blender primitives GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV + + global conversionScale + + units_factor = {'M': 1.0, 'DM': 0.1, 'CM': 0.01, 'MM': 0.001, 'IN': 0.0254} + if file_unit == 'CUSTOM': + conversionScale = global_scale + else: + conversionScale = units_factor[file_unit] # NOTE - reset material cache # (otherwise we might get "StructRNA of type Material has been removed" errors) @@ -3733,6 +3754,8 @@ def load_with_profiler( def load(context, filepath, *, + file_unit='M', + global_scale=1.0, global_matrix=None ): @@ -3740,6 +3763,8 @@ def load(context, load_web3d(context, filepath, PREF_FLAT=True, PREF_CIRCLE_DIV=16, + file_unit=file_unit, + global_scale=global_scale, global_matrix=global_matrix, ) -- 2.30.2 From 82b09447a03c37c2dbc441be96fa8bdb0f62abfc Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Mon, 9 Sep 2024 00:58:14 +0200 Subject: [PATCH 2/3] Code cleanup + various improvements, as requested in PR - tooltips updated - Scale widget now only sensitive when Unit is set to CUSTOM - Unit's scale is reflected in the Scale widget - default values of primitive are now scaled too --- source/__init__.py | 16 ++++++++-- source/import_x3d.py | 69 ++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/source/__init__.py b/source/__init__.py index 546cb83..f187e11 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -39,12 +39,12 @@ class ImportX3D(bpy.types.Operator, ImportHelper): file_unit: EnumProperty( name="File Unit", - items=(('M', "Meter", "The VRML/x3D specification states that data have to be exported in Meter."), + items=(('M', "Meter", "Meter is the default unit of vrml (wrl) files. Most programs assume the objects to be defined in meters as per specification"), ('DM', "Decimeter", ""), ('CM', "Centimeter", ""), ('MM', "Milimeter", ""), ('IN', "Inch", ""), - ('CUSTOM', "Use scale", "Prefer scaling the geometry with the scale factor below"), + ('CUSTOM', "CUSTOM", "Use the scale factor provided below"), ), description="Unit used in the input file", default='M', @@ -54,6 +54,8 @@ class ImportX3D(bpy.types.Operator, ImportHelper): name="Scale", min=0.001, max=1000.0, default=1.0, + precision=4, + step=1.0, description="Scale value used when 'File Unit' is set to 'Use scale'", ) @@ -62,6 +64,7 @@ class ImportX3D(bpy.types.Operator, ImportHelper): keywords = self.as_keywords(ignore=("axis_forward", "axis_up", + "file_unit", "filter_glob", )) global_matrix = axis_conversion(from_forward=self.axis_forward, @@ -269,7 +272,14 @@ class X3D_PT_import_transform(bpy.types.Panel): operator = sfile.active_operator layout.prop(operator, "file_unit") - layout.prop(operator, "global_scale") + + sub = layout.row() + sub.enabled = operator.file_unit == 'CUSTOM' + + sub.prop(operator, "global_scale") + UNITS_FACTOR = {'M': 1.0, 'DM': 0.1, 'CM': 0.01, 'MM': 0.001, 'IN': 0.0254} + if operator.file_unit != 'CUSTOM': + operator.global_scale = UNITS_FACTOR[operator.file_unit] layout.prop(operator, "axis_forward") layout.prop(operator, "axis_up") diff --git a/source/import_x3d.py b/source/import_x3d.py index baee73c..d9ade8a 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -15,7 +15,7 @@ from itertools import chain texture_cache = {} material_cache = {} -conversionScale = 1.0 +conversion_scale = 1.0 EPSILON = 0.0000001 # Very crude. @@ -682,7 +682,7 @@ class vrmlNode(object): print('\tvalue "%s" could not be used as an int for field "%s"' % (f[0], field)) return default - def getFieldAsFloat(self, field, default, ancestry, s=1.0): + def getFieldAsFloat(self, field, default, ancestry, scale_factor=1.0): self_real = self.getRealNode() # in case we're an instance f = self_real.getFieldName(field, ancestry) @@ -696,12 +696,12 @@ class vrmlNode(object): return default try: - return float(f[0])*s + return float(f[0])*scale_factor except: print('\tvalue "%s" could not be used as a float for field "%s"' % (f[0], field)) return default - def getFieldAsFloatTuple(self, field, default, ancestry, s=1.0): + def getFieldAsFloatTuple(self, field, default, ancestry, scale_factor=1.0): self_real = self.getRealNode() # in case we're an instance f = self_real.getFieldName(field, ancestry) @@ -717,7 +717,7 @@ class vrmlNode(object): for v in f: if v != ',': try: - ret.append(float(v.strip('"'))*s) + ret.append(float(v.strip('"'))*scale_factor) except: break # quit of first non float, perhaps its a new field name on the same line? - if so we are going to ignore it :/ TODO # print(ret) @@ -775,7 +775,7 @@ class vrmlNode(object): print('\tvalue "%s" could not be used as a string for field "%s"' % (f[0], field)) return default - def getFieldAsArray(self, field, group, ancestry, s=1.0): + def getFieldAsArray(self, field, group, ancestry, scale_factor=1.0): """ For this parser arrays are children """ @@ -815,7 +815,7 @@ class vrmlNode(object): # print(child_array) # Normal vrml array_data = child_array.array_data - apply_scale = s != 1.0 + apply_scale = scale_factor != 1.0 # print('array_data', array_data) if group == -1 or len(array_data) == 0: @@ -834,7 +834,7 @@ class vrmlNode(object): if apply_scale: # applying scale for item in array_data: - item *= s + item *= scale_factor else: flat_array = [] @@ -846,7 +846,7 @@ class vrmlNode(object): else: if apply_scale: # applying scale - item *= s + item *= scale_factor flat_array.append(item) @@ -1509,11 +1509,11 @@ def translateScale(sca): def translateTransform(node, ancestry): - cent = node.getFieldAsFloatTuple('center', None, ancestry, conversionScale) # (0.0, 0.0, 0.0) + cent = node.getFieldAsFloatTuple('center', None, ancestry, conversion_scale) # (0.0, 0.0, 0.0) rot = node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) - sca = node.getFieldAsFloatTuple('scale', None, ancestry, conversionScale) # (1.0, 1.0, 1.0) + sca = node.getFieldAsFloatTuple('scale', None, ancestry, conversion_scale) # (1.0, 1.0, 1.0) scaori = node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) - tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversionScale) # (0.0, 0.0, 0.0) + tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversion_scale) # (0.0, 0.0, 0.0) if cent: cent_mat = Matrix.Translation(cent) @@ -1553,10 +1553,10 @@ def translateTransform(node, ancestry): def translateTexTransform(node, ancestry): - cent = node.getFieldAsFloatTuple('center', None, ancestry, conversionScale) # (0.0, 0.0) + cent = node.getFieldAsFloatTuple('center', None, ancestry, conversion_scale) # (0.0, 0.0) rot = node.getFieldAsFloat('rotation', None, ancestry) # 0.0 sca = node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0) - tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversionScale) # (0.0, 0.0) + tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversion_scale) # (0.0, 0.0) if cent: # cent is at a corner by default @@ -1930,7 +1930,7 @@ def importMesh_IndexedFaceSet(geom, ancestry): # TODO: resolve that somehow, so that vertex set can be effectively # reused between different mesh types? else: - points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) + points = coord.getFieldAsArray('point', 3, ancestry, conversion_scale) if coord.canHaveReferences(): coord.parsed = points index = geom.getFieldAsArray('coordIndex', 0, ancestry) @@ -2441,7 +2441,7 @@ def importMesh_LineSet(geom, ancestry): # TODO: line display properties are ignored # Per-vertex color is ignored coord = geom.getChildBySpec('Coordinate') - src_points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) + src_points = coord.getFieldAsArray('point', 3, ancestry, conversion_scale) # Array of 3; Blender needs arrays of 4 bpycurve = bpy.data.curves.new("LineSet", 'CURVE') bpycurve.dimensions = '3D' @@ -2467,7 +2467,7 @@ def importMesh_IndexedLineSet(geom, ancestry): # coord = geom.getChildByName('coord') # 'Coordinate' coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml if coord: - points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) + points = coord.getFieldAsArray('point', 3, ancestry, conversion_scale) else: points = [] @@ -2510,7 +2510,7 @@ def importMesh_PointSet(geom, ancestry): # VRML not x3d coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml if coord: - points = coord.getFieldAsArray('point', 3, ancestry, conversionScale) + points = coord.getFieldAsArray('point', 3, ancestry, conversion_scale) else: points = [] @@ -2540,7 +2540,7 @@ def importMesh_Sphere(geom, ancestry): # solid is ignored. # Extra field 'subdivision="n m"' attribute, specifying how many # rings and segments to use (X3DOM). - r = geom.getFieldAsFloat('radius', 0.5, ancestry, conversionScale) + r = geom.getFieldAsFloat('radius', 0.5*conversion_scale, ancestry, conversion_scale) subdiv = geom.getFieldAsArray('subdivision', 0, ancestry) if subdiv: if len(subdiv) == 1: @@ -2635,8 +2635,8 @@ def importMesh_Cylinder(geom, ancestry): # solid is ignored # no ccw in this element # Extra parameter subdivision="n" - how many faces to use - radius = geom.getFieldAsFloat('radius', 1.0, ancestry, conversionScale) - height = geom.getFieldAsFloat('height', 2, ancestry, conversionScale) + radius = geom.getFieldAsFloat('radius', 1.0*conversion_scale, ancestry, conversion_scale) + height = geom.getFieldAsFloat('height', 2.0*conversion_scale, ancestry, conversion_scale) bottom = geom.getFieldAsBool('bottom', True, ancestry) side = geom.getFieldAsBool('side', True, ancestry) top = geom.getFieldAsBool('top', True, ancestry) @@ -2693,8 +2693,8 @@ def importMesh_Cone(geom, ancestry): # Solid ignored # Extra parameter subdivision="n" - how many faces to use n = geom.getFieldAsInt('subdivision', GLOBALS['CIRCLE_DETAIL'], ancestry) - radius = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry, conversionScale) - height = geom.getFieldAsFloat('height', 2, ancestry, conversionScale) + radius = geom.getFieldAsFloat('bottomRadius', 1.0*conversion_scale, ancestry, conversion_scale) + height = geom.getFieldAsFloat('height', 2.0*conversion_scale, ancestry, conversion_scale) bottom = geom.getFieldAsBool('bottom', True, ancestry) side = geom.getFieldAsBool('side', True, ancestry) @@ -2733,7 +2733,7 @@ def importMesh_Cone(geom, ancestry): def importMesh_Box(geom, ancestry): # Solid is ignored # No ccw in this element - (dx, dy, dz) = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry, conversionScale) + (dx, dy, dz) = geom.getFieldAsFloatTuple('size', (2.0*conversion_scale, 2.0*conversion_scale, 2.0*conversion_scale), ancestry, conversion_scale) dx /= 2 dy /= 2 dz /= 2 @@ -3286,9 +3286,9 @@ def importLamp_PointLight(node, ancestry): # attenuation = node.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry) intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher. - location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversionScale) + location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversion_scale) # is_on = node.getFieldAsBool('on', True, ancestry) # TODO - radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversionScale) + radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversion_scale) bpylamp = bpy.data.lights.new(vrmlname, 'POINT') bpylamp.energy = intensity @@ -3335,9 +3335,9 @@ def importLamp_SpotLight(node, ancestry): cutOffAngle = node.getFieldAsFloat('cutOffAngle', 0.785398, ancestry) * 2.0 # max is documented to be 1.0 but some files have higher. direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry) intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher. - location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversionScale) + location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry, conversion_scale) # is_on = node.getFieldAsBool('on', True, ancestry) # TODO - radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversionScale) + radius = node.getFieldAsFloat('radius', 100.0, ancestry, conversion_scale) bpylamp = bpy.data.lights.new(vrmlname, 'SPOT') bpylamp.energy = intensity @@ -3389,7 +3389,7 @@ def importViewpoint(bpycollection, node, ancestry, global_matrix): fieldOfView = node.getFieldAsFloat('fieldOfView', 0.785398, ancestry) # max is documented to be 1.0 but some files have higher. # jump = node.getFieldAsBool('jump', True, ancestry) orientation = node.getFieldAsFloatTuple('orientation', (0.0, 0.0, 1.0, 0.0), ancestry) - position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry, conversionScale) + position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry, conversion_scale) description = node.getFieldAsString('description', '', ancestry) bpycam = bpy.data.cameras.new(name) @@ -3610,13 +3610,8 @@ def load_web3d( # Used when adding blender primitives GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV - global conversionScale - - units_factor = {'M': 1.0, 'DM': 0.1, 'CM': 0.01, 'MM': 0.001, 'IN': 0.0254} - if file_unit == 'CUSTOM': - conversionScale = global_scale - else: - conversionScale = units_factor[file_unit] + global conversion_scale + conversion_scale = global_scale # NOTE - reset material cache # (otherwise we might get "StructRNA of type Material has been removed" errors) @@ -3754,7 +3749,6 @@ def load_with_profiler( def load(context, filepath, *, - file_unit='M', global_scale=1.0, global_matrix=None ): @@ -3763,7 +3757,6 @@ def load(context, load_web3d(context, filepath, PREF_FLAT=True, PREF_CIRCLE_DIV=16, - file_unit=file_unit, global_scale=global_scale, global_matrix=global_matrix, ) -- 2.30.2 From 5054c3168975f1fa8336215475c5e01fc394e1cc Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Mon, 9 Sep 2024 22:41:36 +0200 Subject: [PATCH 3/3] Various bugfix: - Fixed tooltip string - importMesh_ReadVertices now also scale geometry (used in other nodes) - Scale in transforms was erroneously scaled - scaling bugfix in getFieldAsArray --- source/__init__.py | 2 +- source/import_x3d.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/__init__.py b/source/__init__.py index f187e11..9090e07 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -56,7 +56,7 @@ class ImportX3D(bpy.types.Operator, ImportHelper): default=1.0, precision=4, step=1.0, - description="Scale value used when 'File Unit' is set to 'Use scale'", + description="Scale value used when 'File Unit' is set to 'CUSTOM'", ) def execute(self, context): diff --git a/source/import_x3d.py b/source/import_x3d.py index d9ade8a..9079580 100644 --- a/source/import_x3d.py +++ b/source/import_x3d.py @@ -815,7 +815,6 @@ class vrmlNode(object): # print(child_array) # Normal vrml array_data = child_array.array_data - apply_scale = scale_factor != 1.0 # print('array_data', array_data) if group == -1 or len(array_data) == 0: @@ -828,13 +827,15 @@ class vrmlNode(object): flat = False break + apply_scale = scale_factor != 1.0 + # make a flat array if flat: - flat_array = array_data # we are already flat. if apply_scale: # applying scale - for item in array_data: - item *= scale_factor + flat_array = [n*scale_factor for n in array_data] # scaling the data + else: + flat_array = array_data # we are already flat. else: flat_array = [] @@ -1511,7 +1512,7 @@ def translateScale(sca): def translateTransform(node, ancestry): cent = node.getFieldAsFloatTuple('center', None, ancestry, conversion_scale) # (0.0, 0.0, 0.0) rot = node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) - sca = node.getFieldAsFloatTuple('scale', None, ancestry, conversion_scale) # (1.0, 1.0, 1.0) + sca = node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0, 1.0) scaori = node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0) tx = node.getFieldAsFloatTuple('translation', None, ancestry, conversion_scale) # (0.0, 0.0, 0.0) @@ -1693,7 +1694,7 @@ def importMesh_ReadVertices(bpymesh, geom, ancestry): # IndexedFaceSet presumes a 2D one. # The case for caching is stronger over there. coord = geom.getChildBySpec('Coordinate') - points = coord.getFieldAsArray('point', 0, ancestry) + points = coord.getFieldAsArray('point', 0, ancestry, conversion_scale) bpymesh.vertices.add(len(points) // 3) bpymesh.vertices.foreach_set("co", points) -- 2.30.2